#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

typedef struct __attribute__((__packed__))
{
	uint32_t			signature;
	uint16_t			machine;
	uint16_t			section_cnt;
	uint32_t			timedate;
	uint32_t			symtbl_offs;
	uint32_t			symtbl_cnt;
	uint16_t			opthdr_size;
	uint16_t			characteristics;
} pe32_hdr;

typedef struct __attribute__((__packed__))
{
	uint32_t			offset;
	uint32_t			size;
} pe32_pair;

typedef struct __attribute__((__packed__))
{
	uint16_t			magic;
	uint16_t			version;
	uint32_t			code_size;
	uint32_t			data_size;
	uint32_t			bss_size;
	uint32_t			entry_special;
	uint32_t			code_baserva;
	uint32_t			data_baserva;
	uint32_t			image_base;
	uint32_t			align_section;
	uint32_t			align_file;
	uint16_t			os_major;
	uint16_t			os_minor;
	uint16_t			usr_major;
	uint16_t			usr_minor;
	uint16_t			subsys_major;
	uint16_t			subsys_minor;
	uint32_t			reserved0;
	uint32_t			size_image;
	uint32_t			size_hdr;
	uint32_t			crc;
	uint16_t			subsys;
	uint16_t			dllflags;
	uint32_t			size_stackreserve;
	uint32_t			size_stackcommit;
	uint32_t			size_heapreserve;
	uint32_t			size_heapcommit;
	uint32_t			loaderflags;
	uint32_t			dd_cnt;
	pe32_pair			dd[16];
} pe32_opthdr;

typedef struct __attribute__((__packed__))
{
	uint8_t			name[8];
	uint32_t			size_v;
	uint32_t			addr_v;
	uint32_t			size;
	uint32_t			addr;
	uint32_t			reloc_rva;
	uint32_t			ln_rva;
	uint16_t			reloc_cnt;
	uint16_t			ln_cnt;
	uint32_t			characteristics;
	
} pe32_shdr;

typedef struct __attribute__((__packed__))
{
	uint32_t			cb;					/// size of this structure in bytes.
	uint32_t			rversion;				/// required run-time version to run this program.
	pe32_pair			metadata;				/// rva and size of the physical metadata.
	uint32_t			flags;				/// flags describing this run-time image.
	uint32_t			entrypoint_token;			/// token for the MethodDef or File of the entry point for the image.
	pe32_pair			resources;				/// rva of implementation specific resources.
	pe32_pair			signature_strong;			/// rva of hash data for this PE file used by CLI loader for binding and versioning.
	uint32_t			codeman_tbl;			/// code manager table
	pe32_pair			vtble_fix;				/// virtual table fixups
	pe32_pair			extbl_jumps;			/// export address table jumps
	pe32_pair			mnh;					/// managed native header
} pe32_cilhdr;

#define STREAM_READ8(spos, stream) *((uint8_t*)(&stream[(spos = spos + 1) - 1]))
#define STREAM_READ16(spos, stream) *((uint16_t*)(&stream[(spos = spos + 2) - 2]))
#define STREAM_READ32(spos, stream) *((uint32_t*)(&stream[(spos = spos + 4) - 4]))

/// <description> computes a file offset from a relative virtual address using the section table and header. </description>
/// <reason> not feasible to load image into memory at specified addresses (RVAs), so a manual translation is done. </reason>
uint32_t pe32_rvat(pe32_hdr *hdr, pe32_opthdr *opthdr, pe32_shdr *shdr, uint32_t vaddr)
{
	uint32_t			x;
	for(x = 0; x < hdr->section_cnt; ++x)
	{
		if(vaddr >= shdr[x].addr_v)
		{
			return (vaddr - shdr[x].addr_v) + shdr[x].addr;
		}
	}
	return 0;
}
/// <description> processes the CIL header. </description>
int processPE32CIL(uint8_t *buffer, uint32_t size, pe32_hdr *hdr, pe32_opthdr *opthdr, pe32_shdr *shdr, pe32_cilhdr *cilhdr)
{
	uint32_t			x, y;
	uint32_t			stream_pos, cilstream_cnt;

	stream_pos = pe32_rvat(hdr, opthdr, shdr, cilhdr->metadata.offset);

	printf("mrtv:%x metadata:%x\n", cilhdr->rversion, cilhdr->metadata);

	printf("signature:%x\n", STREAM_READ32(stream_pos, buffer));
	printf("major-version:%x\n", STREAM_READ16(stream_pos, buffer));
	printf("minor-version:%x\n", STREAM_READ16(stream_pos, buffer));
	// skip reserved
	stream_pos += 4;
	y = STREAM_READ32(stream_pos, buffer);
	// read version string with a length that is a multiple of four.
	for(x = 0; x < ((y%4) > 0 ? y + (4 - (y%4)) : y); ++x)
	{
		printf("%c", STREAM_READ8(stream_pos, buffer));
	}
	printf("\n");
	// skip flags
	stream_pos += 2;
	// read CIL streams
	cilstream_cnt = STREAM_READ16(stream_pos, buffer);
	printf("cilstream_cnt:%x\n", cilstream_cnt);
	for(x = 0; x < cilstream_cnt; ++x)
	{
		printf("stream-offset:%x\n", STREAM_READ32(stream_pos, buffer));
		printf("stream-size:%x\n", STREAM_READ32(stream_pos, buffer));
		printf("stream-name:");


		for(y = 0; buffer[stream_pos] != 0; ++y, ++stream_pos)
		{
			printf("%c", buffer[stream_pos]);
		}
		for(; buffer[stream_pos] == 0; ++stream_pos);
		printf("\n");
	}

	return 1;
}
/// <description> processes a PE32 file for the CIL data. </description>
int processPE32(uint8_t *buffer, uint32_t size)
{
	uint32_t			x;
	pe32_hdr			*hdr;
	pe32_opthdr			*opthdr;
	pe32_shdr			*shdr;
	pe32_cilhdr			*cilhdr;
	// get offset to pe32 signature.
	hdr = (pe32_hdr*)&buffer[*((uint32_t*)&buffer[0x3c])];
	if(hdr->signature != 0x00004550)
	{
		fprintf(stderr, "error: image does not contain a pe32 signature.\n");
		return -1;
	}
	printf("machine:%x section_cnt:%x timedate:%x symtbl_offs:%x symtbl_cnt:%x opthdr_size:%x\n",
		hdr->machine, hdr->section_cnt, hdr->timedate, hdr->symtbl_offs, hdr->symtbl_cnt, hdr->opthdr_size);
	if(hdr->opthdr_size < sizeof(pe32_opthdr))
	{
		fprintf(stderr, "error: image header does not contain a optional header large enough to support the CIL extension.\n");
		return -1;
	}
	opthdr = (pe32_opthdr*)((uintptr_t)hdr + sizeof(pe32_hdr));
	// check data directory index 14 for CLI header.
	if(!opthdr->dd[14].offset && !opthdr->dd[14].size)
	{
		fprintf(stderr, "error: image optional header does not contain a pointer to a CIL header.\n");
		return -1;
	}
	// display sections for diagnostic purposes.
	shdr = (pe32_shdr*)((uintptr_t)opthdr + hdr->opthdr_size);
	for(x = 0; x < hdr->section_cnt; ++x)
	{
		shdr[x].name[7] = 0;
		printf("section(%x): addr_v:%x addr:%x size:%x name:%s\n", x, shdr[x].addr_v, shdr[x].addr, shdr[x].size, shdr[x].name);
	}
	printf("cil header RVA is %x.\n", opthdr->dd[14].offset);
	cilhdr = (pe32_cilhdr*)(pe32_rvat(hdr, opthdr, shdr, opthdr->dd[14].offset) + (uintptr_t)buffer);
	return processPE32CIL(buffer, size, hdr, opthdr, shdr, cilhdr);
}

/// <description> reads entire input file into memory for processing. </description>
int main(int argc, char *argv[])
{
	struct stat		fd_stat;
	int 			fd;
	uint8_t		*buffer;
	printf("%s: loading %s.\n", argv[0], argv[1]);	
	fd = open(argv[1], O_RDONLY);
	fstat(fd, &fd_stat);
	buffer = (uint8_t*)malloc(fd_stat.st_size);
	read(fd, buffer, fd_stat.st_size);
	close(fd);
	return processPE32(buffer, fd_stat.st_size);
}


